from typing import Optional

from fastapi import HTTPException, status
from tortoise.exceptions import IntegrityError
from tortoise.transactions import atomic

from app.crud.users import create_user, delete_user, update_user, verify_user
from app.models.doctors import Doctor, Modality
from app.schemas.doctors import (
    DoctorCreateSchema,
    DoctorUpdateSchema,
    ModalityCreateSchema,
    ModalityUpdateSchema,
)


async def create_modality(modality_schema: ModalityCreateSchema) -> Modality:
    try:
        return await Modality.create(**modality_schema.model_dump(exclude_unset=True))
    except IntegrityError:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Modality already exists")


async def get_modality_by_code(code: str) -> Optional[Modality]:
    return await Modality.get_or_none(code=code)


async def get_all_modalities() -> list[Modality]:
    return await Modality.all()


async def get_modalities_by_codes(codes: list[str]) -> list[Modality]:
    return await Modality.filter(code__in=codes)


async def update_modality(modality: Modality, modality_schema: ModalityUpdateSchema) -> Modality:
    updated_modality = modality.update_from_dict(modality_schema.model_dump(exclude_unset=True))
    await updated_modality.save()
    return updated_modality


async def delete_modality(modality: Modality) -> None:
    await modality.delete()


@atomic()
async def create_doctor(doctor_schema: DoctorCreateSchema) -> Doctor:
    doctor_dict = doctor_schema.model_dump(exclude_unset=True)
    user = await create_user(doctor_schema.create_user_schema)
    doctor_dict.pop("create_user_schema")
    main_modality = await get_modality_by_code(doctor_schema.main_modality_code)
    doctor_dict.pop("main_modality_code")
    additional_modalities = await get_modalities_by_codes(doctor_schema.additional_modalities_codes)
    doctor_dict.pop("additional_modalities_codes")
    new_doctor = Doctor(user=user, main_modality=main_modality, **doctor_dict)
    try:
        await new_doctor.save()
    except IntegrityError:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Doctor already exists")
    await new_doctor.additional_modalities.add(*additional_modalities)
    await new_doctor.fetch_related("user", "main_modality", "additional_modalities")
    return new_doctor


async def get_doctor_by_user_id(user_id: int) -> Optional[Doctor]:
    return await Doctor.get_or_none(user_id=user_id).prefetch_related("user", "main_modality", "additional_modalities")


async def get_all_doctors(offset: int = 0, limit: int = 40) -> list[Doctor]:
    return (
        await Doctor.all()
        .offset(offset)
        .limit(limit)
        .prefetch_related("user", "main_modality", "additional_modalities")
    )


async def get_doctors_by_main_modality_codes(
    modality_codes: list[str], offset: int = 0, limit: int = 40
) -> list[Doctor]:
    return (
        await Doctor.filter(main_modality__code__in=modality_codes)
        .offset(offset)
        .limit(limit)
        .prefetch_related("user", "main_modality", "additional_modalities")
    )


async def get_doctors_by_additional_modality_codes(
    modality_codes: list[str], offset: int = 0, limit: int = 40
) -> list[Doctor]:
    return (
        await Doctor.filter(additional_modalities__code__in=modality_codes)
        .offset(offset)
        .limit(limit)
        .prefetch_related("user", "main_modality", "additional_modalities")
    )


@atomic()
async def update_doctor(doctor: Doctor, doctor_schema: DoctorUpdateSchema) -> Doctor:
    doctor_dict = doctor_schema.model_dump(exclude_unset=True)
    if doctor_schema.update_user_schema is not None:
        await update_user(doctor.user, doctor_schema.update_user_schema)
        doctor_dict.pop("update_user_schema")
    if doctor_dict.get("main_modality_code"):
        doctor_dict["main_modality"] = await get_modality_by_code(doctor_dict.pop("main_modality_code"))
    if doctor_dict.get("additional_modalities_codes"):
        additional_modalities = await get_modalities_by_codes(doctor_dict.pop("additional_modalities_codes"))
        await doctor.additional_modalities.clear()
        await doctor.additional_modalities.add(*additional_modalities)
    updated_doctor = doctor.update_from_dict(doctor_dict)
    await updated_doctor.save()
    await updated_doctor.fetch_related("user", "main_modality", "additional_modalities")
    return updated_doctor


async def delete_doctor_by_id(user_id: int) -> None:
    await delete_user(user_id)  # Doctor will be deleted by CASCADE


async def verify_doctor(user_id: int) -> bool:
    return await verify_user(user_id)
